#pragma once

#include<iostream>
#include<string>
#include"Mutex.hpp"
#include<filesystem>
#include<fstream>
#include<time.h>
#include<memory>
#include<sstream>
#include<unistd.h>

enum class LogLevel
{
    DEBUG,
    INFO,
    WARNING,
    ERROR,
    FATAL
};

std::string LevelString(LogLevel level)
{
    if(level == LogLevel::DEBUG)
        return "DEBUG";
    else if(level == LogLevel::ERROR)
        return "ERROR";
    else if(level == LogLevel::INFO)
        return "INFO";
    else if(level == LogLevel::FATAL)
        return "FATAL";
    else if(level == LogLevel::WARNING)
        return "WARNING";
    else
        return "Unknown";
}

std::string GetCurrentTime()
{
    time_t cur_time = time(nullptr);
    struct tm ret;
    localtime_r(&cur_time, &ret);
    char buff[64];
    snprintf(buff, sizeof(buff), "%d-%02d-%02d %02d:%02d:%02d",
             ret.tm_year + 1900,
             ret.tm_mon + 1,
             ret.tm_mday,
             ret.tm_hour,
             ret.tm_min,
             ret.tm_sec);
    return buff;
}

class LogStrategy
{
public:
    virtual ~LogStrategy() = default;
    virtual void SyncLog(const std::string& log_message) = 0;
};

class ConsoleLogStrategy : public LogStrategy
{
public:
    void SyncLog(const std::string& log_message)
    {
        LockGuard lock_guard(&_lock);
        std::cout << log_message << std::endl;
    }
private:
    Mutex _lock;
};

const std::string log_default_dir_pathname = "log";
const std::string log_default_filename = "test.log";

class FileLogStrategy : public LogStrategy
{
public:
    FileLogStrategy(const std::string dir_pathname = log_default_dir_pathname, 
                    const std::string filename = log_default_filename)
        :_dir_pathname(dir_pathname)
        ,_filename(filename)
    { 
        LockGuard lock_guard(&_lock);
        if(std::filesystem::exists(_dir_pathname))
            return;
        try
        {   
            std::filesystem::create_directories(_dir_pathname);
        }
        catch(const std::filesystem::filesystem_error& e)
        {
            std::cerr << e.what() << std::endl;
        }
    }

    void SyncLog(const std::string& log_message)
    {
        LockGuard lock_guard(&_lock);
        std::string target = _dir_pathname;
        target += '/';
        target += _filename;
        std::ofstream out(target.c_str(), std::ios::app);
        if(!out.is_open())
            return;
        out << log_message << std::endl;
        out.close();
    }

private:
    std::string _dir_pathname;
    std::string _filename;
    Mutex _lock;
};

class Logger
{
public:
    void EnableConsoleLogStrategy()
    {
        _strategy = std::make_unique<ConsoleLogStrategy>();
    }

    void EnableFileLogStrategy()
    {
        _strategy = std::make_unique<FileLogStrategy>();
    }

    class LogMessage
    {
    public:
        LogMessage(LogLevel level, std::string filename, int line, Logger& logger)
            :_cur_time(GetCurrentTime())
            ,_level(level)
            ,_pid(getpid())
            ,_filename(filename)
            ,_line(line)
            ,_logger(logger)
        { 
            std::stringstream ss;
            ss << "[" << _cur_time << "] "
               << "[" << LevelString(_level) << "] "
               << "[" << _pid << "] "
               << "[" << _filename << "] "
               << "[" << _line << "] "
               << "- ";
            _log_info = ss.str();
        }

        template<class T>
        LogMessage& operator<<(const T& info)
        {
            std::stringstream ss;
            ss << info;
            _log_info += ss.str();
            return *this;
        }

        ~LogMessage()
        {
            if(_logger._strategy)
            {
                _logger._strategy->SyncLog(_log_info);
            }
        }

    private:
        std::string _cur_time;
        LogLevel _level;
        pid_t _pid;
        std::string _filename;
        int _line;
        std::string _log_info;
        Logger& _logger;
    };

    LogMessage operator()(LogLevel level, std::string filename, int line)
    {
        return LogMessage(level, filename, line, *this);
    }

private:
    std::unique_ptr<LogStrategy> _strategy;
};

Logger logger;

#define LOG(level) logger(level, __FILE__, __LINE__)
#define EnableConsoleLogStrategy() logger.EnableConsoleLogStrategy()
#define EnableFileLogStrategy() logger.EnableFileLogStrategy()